Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SPA requests by routing 404 to /index.html #44

Merged
merged 1 commit into from
Dec 12, 2023

Conversation

TylerHendrickson
Copy link
Member

This PR addresses an issue where requests to top-level routes like /agencies, /organizations, /agencies/new, etc. resolve a 404 response. Since React routing determines which view to serve based on the URI, this PR configures the CDN to serve /index.html (which loads the React app) whenever a request doesn't match an origin key (i.e. an S3 object).

Caveat for this behavior: since the CDN can only differentiate between requests that match S3 object keys and those that don't, it is unable to determine whether an arbitrary path like /maybe-this-exists is valid as far as React is concerned. Although React's ability to serve the correct view (including potentially falling back to the "Not Found" view) is not impacted, it does mean that all requests to the CDN will respond with a 200 status code, even when the requested route is truly invalid.

@TylerHendrickson TylerHendrickson self-assigned this Dec 12, 2023
@TylerHendrickson TylerHendrickson requested a review from a team as a code owner December 12, 2023 01:29
@github-actions github-actions bot added bug Something isn't working infra terraform labels Dec 12, 2023
Copy link

Terraform Summary

Step Result
🖌 Terraform Format & Style
⚙️ Terraform Initialization
🤖 Terraform Validation
📖 Terraform Plan

Hint: If "Terraform Format & Style" failed, run terraform fmt -recursive from the terraform/ directory and commit the results.

Output

Validation Output
Success! The configuration is valid.


Plan Output
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+   create
  ~ update in-place
-   destroy
-/+ destroy and then create replacement
+/- create replacement and then destroy

Terraform will perform the following actions:

  # aws_cloudfront_function.web_uri_rewriter will be destroyed
  # (because aws_cloudfront_function.web_uri_rewriter is not in configuration)
-   resource "aws_cloudfront_function" "web_uri_rewriter" {
-       arn             = "arn:aws:cloudfront::357150818708:function/cpfreporter-web_uri_rewriter" -> null
-       code            = <<-EOT
            function handler(event) {
                var request = event.request;
                var uri = request.uri;
            
                if (uri === '') {
                    request.uri = 'index.html'
                } else if (uri.endsWith('/')) {
                    request.uri += 'index.html';
                } else if (!uri.includes('.')) {
                    request.uri += '/index.html';
                }
            
                return request;
            }
        EOT -> null
-       comment         = "Rewrites URIs to be SPA-friendly." -> null
-       etag            = "ETVPDKIKX0DER" -> null
-       id              = "cpfreporter-web_uri_rewriter" -> null
-       live_stage_etag = "ETVPDKIKX0DER" -> null
-       name            = "cpfreporter-web_uri_rewriter" -> null
-       publish         = true -> null
-       runtime         = "cloudfront-js-1.0" -> null
-       status          = "UNASSOCIATED" -> null
    }

  # aws_ecs_service.console will be updated in-place
  ~ resource "aws_ecs_service" "console" {
        id                                 = "arn:aws:ecs:us-west-2:357150818708:service/cpfreporter/cpfreporter-console"
        name                               = "cpfreporter-console"
        tags                               = {}
      ~ task_definition                    = "arn:aws:ecs:us-west-2:357150818708:task-definition/cpfreporter-console:17" -> (known after apply)
        # (15 unchanged attributes hidden)

        # (3 unchanged blocks hidden)
    }

  # aws_ecs_task_definition.console must be replaced
+/- resource "aws_ecs_task_definition" "console" {
      ~ arn                      = "arn:aws:ecs:us-west-2:357150818708:task-definition/cpfreporter-console:17" -> (known after apply)
      ~ arn_without_revision     = "arn:aws:ecs:us-west-2:357150818708:task-definition/cpfreporter-console" -> (known after apply)
      ~ container_definitions    = (sensitive value) # forces replacement
      ~ id                       = "cpfreporter-console" -> (known after apply)
      ~ revision                 = 17 -> (known after apply)
-       tags                     = {} -> null
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_s3_object.lambda_artifact-graphql will be updated in-place
  ~ resource "aws_s3_object" "lambda_artifact-graphql" {
      ~ etag                   = "fbc5e47504b58d94b686c1d8df88fd11-6" -> "d1530f86ca61c86a26fa654c0a46f7e8"
        id                     = "graphql.d1530f86ca61c86a26fa654c0a46f7e8.zip"
        tags                   = {}
      ~ version_id             = "gM3E1o8SkZK_9JodCh9alFNY6RSNto89" -> (known after apply)
        # (11 unchanged attributes hidden)
    }

  # aws_s3_object.origin_dist_artifact["200.html"] will be updated in-place
  ~ resource "aws_s3_object" "origin_dist_artifact" {
      ~ etag                   = "4ec39a1802ad471c9281759bd4d36065" -> "1310e8797d11fa22899d647f10403899"
        id                     = "dist/200.html"
      ~ source_hash            = "4ec39a1802ad471c9281759bd4d36065" -> "1310e8797d11fa22899d647f10403899"
        tags                   = {}
      ~ version_id             = "_baMlvstTyi2XQweXn78s7PQM9wijQRJ" -> (known after apply)
        # (10 unchanged attributes hidden)
    }

  # aws_s3_object.origin_dist_artifact["build-manifest.json"] will be updated in-place
  ~ resource "aws_s3_object" "origin_dist_artifact" {
      ~ etag                   = "ed052c866c3987b7c121febf9a40473f" -> "8f5c9178fa80f14d15cd3a32f819dbee"
        id                     = "dist/build-manifest.json"
      ~ source_hash            = "ed052c866c3987b7c121febf9a40473f" -> "8f5c9178fa80f14d15cd3a32f819dbee"
        tags                   = {}
      ~ version_id             = "Zr9Nmlcx7IjSo280o1uLRHxLZmPqgZXk" -> (known after apply)
        # (10 unchanged attributes hidden)
    }

  # aws_s3_object.origin_dist_artifact["chunk-references.json"] will be updated in-place
  ~ resource "aws_s3_object" "origin_dist_artifact" {
      ~ etag                   = "3eea0142653e5f3f036aea89f80d9e97" -> "54f38202789932549d203cfac8d15867"
        id                     = "dist/chunk-references.json"
      ~ source_hash            = "3eea0142653e5f3f036aea89f80d9e97" -> "54f38202789932549d203cfac8d15867"
        tags                   = {}
      ~ version_id             = "GAWw7obC_59atTMF33It48aIluklWW7S" -> (known after apply)
        # (10 unchanged attributes hidden)
    }

  # aws_s3_object.origin_dist_artifact["index.html"] will be updated in-place
  ~ resource "aws_s3_object" "origin_dist_artifact" {
      ~ etag                   = "4ec39a1802ad471c9281759bd4d36065" -> "1310e8797d11fa22899d647f10403899"
        id                     = "dist/index.html"
      ~ source_hash            = "4ec39a1802ad471c9281759bd4d36065" -> "1310e8797d11fa22899d647f10403899"
        tags                   = {}
      ~ version_id             = "eTUky2YyF4UsUz7U9PHKdwf6b7rCuYrg" -> (known after apply)
        # (10 unchanged attributes hidden)
    }

  # aws_s3_object.origin_dist_artifact["static/js/app.74432613.js"] will be created
+   resource "aws_s3_object" "origin_dist_artifact" {
+       acl                    = (known after apply)
+       bucket                 = "cpfreporter-origin-357150818708-us-west-2"
+       bucket_key_enabled     = (known after apply)
+       checksum_crc32         = (known after apply)
+       checksum_crc32c        = (known after apply)
+       checksum_sha1          = (known after apply)
+       checksum_sha256        = (known after apply)
+       content_type           = "text/javascript"
+       etag                   = "dd42626cf22d4c58fc5e205928b29a40"
+       force_destroy          = false
+       id                     = (known after apply)
+       key                    = "dist/static/js/app.74432613.js"
+       kms_key_id             = (known after apply)
+       server_side_encryption = "AES256"
+       source                 = "/home/runner/work/cpf-reporter/cpf-reporter/web/dist/static/js/app.74432613.js"
+       source_hash            = "dd42626cf22d4c58fc5e205928b29a40"
+       storage_class          = (known after apply)
+       tags_all               = {
+           "env"        = "staging"
+           "management" = "terraform"
+           "owner"      = "grants"
+           "repo"       = "cpf-reporter"
+           "service"    = "cpf-reporter"
+           "usage"      = "workload"
        }
+       version_id             = (known after apply)
    }

  # aws_s3_object.origin_dist_artifact["static/js/app.74432613.js.LICENSE.txt"] will be created
+   resource "aws_s3_object" "origin_dist_artifact" {
+       acl                    = (known after apply)
+       bucket                 = "cpfreporter-origin-357150818708-us-west-2"
+       bucket_key_enabled     = (known after apply)
+       checksum_crc32         = (known after apply)
+       checksum_crc32c        = (known after apply)
+       checksum_sha1          = (known after apply)
+       checksum_sha256        = (known after apply)
+       content_type           = "text/plain"
+       etag                   = "8d0c60a3ca38489c1bb3fd646469d4db"
+       force_destroy          = false
+       id                     = (known after apply)
+       key                    = "dist/static/js/app.74432613.js.LICENSE.txt"
+       kms_key_id             = (known after apply)
+       server_side_encryption = "AES256"
+       source                 = "/home/runner/work/cpf-reporter/cpf-reporter/web/dist/static/js/app.74432613.js.LICENSE.txt"
+       source_hash            = "8d0c60a3ca38489c1bb3fd646469d4db"
+       storage_class          = (known after apply)
+       tags_all               = {
+           "env"        = "staging"
+           "management" = "terraform"
+           "owner"      = "grants"
+           "repo"       = "cpf-reporter"
+           "service"    = "cpf-reporter"
+           "usage"      = "workload"
        }
+       version_id             = (known after apply)
    }

  # aws_s3_object.origin_dist_artifact["static/js/app.fddc8506.js"] will be destroyed
  # (because key ["static/js/app.fddc8506.js"] is not in for_each map)
-   resource "aws_s3_object" "origin_dist_artifact" {
-       bucket                 = "cpfreporter-origin-357150818708-us-west-2" -> null
-       bucket_key_enabled     = false -> null
-       content_type           = "text/javascript" -> null
-       etag                   = "95df2fdec669243ddc052c67331c2479" -> null
-       force_destroy          = false -> null
-       id                     = "dist/static/js/app.fddc8506.js" -> null
-       key                    = "dist/static/js/app.fddc8506.js" -> null
-       metadata               = {} -> null
-       server_side_encryption = "AES256" -> null
-       source                 = "/home/runner/work/cpf-reporter/cpf-reporter/web/dist/static/js/app.fddc8506.js" -> null
-       source_hash            = "95df2fdec669243ddc052c67331c2479" -> null
-       storage_class          = "STANDARD" -> null
-       tags                   = {} -> null
-       tags_all               = {
-           "env"        = "staging"
-           "management" = "terraform"
-           "owner"      = "grants"
-           "repo"       = "cpf-reporter"
-           "service"    = "cpf-reporter"
-           "usage"      = "workload"
        } -> null
-       version_id             = "HWe661EYZClsS9MZmKUvjFZwcD9d58.p" -> null
    }

  # aws_s3_object.origin_dist_artifact["static/js/app.fddc8506.js.LICENSE.txt"] will be destroyed
  # (because key ["static/js/app.fddc8506.js.LICENSE.txt"] is not in for_each map)
-   resource "aws_s3_object" "origin_dist_artifact" {
-       bucket                 = "cpfreporter-origin-357150818708-us-west-2" -> null
-       bucket_key_enabled     = false -> null
-       content_type           = "text/plain" -> null
-       etag                   = "8d0c60a3ca38489c1bb3fd646469d4db" -> null
-       force_destroy          = false -> null
-       id                     = "dist/static/js/app.fddc8506.js.LICENSE.txt" -> null
-       key                    = "dist/static/js/app.fddc8506.js.LICENSE.txt" -> null
-       metadata               = {} -> null
-       server_side_encryption = "AES256" -> null
-       source                 = "/home/runner/work/cpf-reporter/cpf-reporter/web/dist/static/js/app.fddc8506.js.LICENSE.txt" -> null
-       source_hash            = "8d0c60a3ca38489c1bb3fd646469d4db" -> null
-       storage_class          = "STANDARD" -> null
-       tags                   = {} -> null
-       tags_all               = {
-           "env"        = "staging"
-           "management" = "terraform"
-           "owner"      = "grants"
-           "repo"       = "cpf-reporter"
-           "service"    = "cpf-reporter"
-           "usage"      = "workload"
        } -> null
-       version_id             = "I1kGO2sWO9jRT1y7sLGgfYQqxsb8QDd8" -> null
    }

  # module.lambda_function-graphql.aws_lambda_function.this[0] will be updated in-place
  ~ resource "aws_lambda_function" "this" {
        id                             = "cpfreporter-graphql"
      ~ qualified_arn                  = "arn:aws:lambda:us-west-2:357150818708:function:cpfreporter-graphql:33" -> (known after apply)
      ~ qualified_invoke_arn           = "arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:357150818708:function:cpfreporter-graphql:33/invocations" -> (known after apply)
        tags                           = {}
      ~ version                        = "33" -> (known after apply)
        # (21 unchanged attributes hidden)

      ~ environment {
          ~ variables = {
              ~ "DD_COMMIT_SHA"                      = "0fdfb6ab87ebda250ffa0c9d14938b1e0ae3d011" -> "7bdcf33771e0c243a6955a104658a463de644c3c"
              ~ "DD_TAGS"                            = "git.commit.sha:0fdfb6ab87ebda250ffa0c9d14938b1e0ae3d011,git.repository_url:github.com/usdigitalresponse/cpf-reporter" -> "git.commit.sha:7bdcf33771e0c243a6955a104658a463de644c3c,git.repository_url:github.com/usdigitalresponse/cpf-reporter"
              ~ "DD_VERSION"                         = "0fdfb6ab87ebda250ffa0c9d14938b1e0ae3d011" -> "7bdcf33771e0c243a6955a104658a463de644c3c"
                # (15 unchanged elements hidden)
            }
        }

        # (4 unchanged blocks hidden)
    }

  # module.lambda_function-graphql.aws_lambda_permission.current_version_triggers["APIGateway"] must be replaced
-/+ resource "aws_lambda_permission" "current_version_triggers" {
      ~ id                  = "APIGateway" -> (known after apply)
      ~ qualifier           = "33" # forces replacement -> (known after apply) # forces replacement
+       statement_id_prefix = (known after apply)
        # (5 unchanged attributes hidden)
    }

Plan: 4 to add, 7 to change, 5 to destroy.

Pusher: @TylerHendrickson, Action: pull_request_target, Workflow: Continuous Integration

Copy link

QA Summary

QA Check Result
🌐 Web Tests
🔗 API Tests
📏 ESLint
🧹 TFLint

Test Coverage

Coverage report for api suite
St File % Stmts % Branch % Funcs % Lines Uncovered Line #s
🟡 All files 70.79 33.33 64.35 70.79
🟢  directives/requireAuth 100 100 100 100
🟢   requireAuth.ts 100 100 100 100
🟡  directives/skipAuth 50 100 0 50
🟡   skipAuth.ts 50 100 0 50 13
🔴  functions 0 100 0 0
🔴   graphql.ts 0 100 0 0 13-20
🔴  graphql 0 100 100 0
🔴   agencies.sdl.ts 0 100 100 0 1
🔴   expenditureCategories.sdl.ts 0 100 100 0 1
🔴   inputTemplates.sdl.ts 0 100 100 0 1
🔴   organizations.sdl.ts 0 100 100 0 1
🔴   outputTemplates.sdl.ts 0 100 100 0 1
🔴   projects.sdl.ts 0 100 100 0 1
🔴   reportingPeriods.sdl.ts 0 100 100 0 1
🔴   roles.sdl.ts 0 100 100 0 1
🔴   subrecipients.sdl.ts 0 100 100 0 1
🔴   uploadValidations.sdl.ts 0 100 100 0 1
🔴   uploads.sdl.ts 0 100 100 0 1
🔴   users.sdl.ts 0 100 100 0 1
🔴  lib 40.74 50 57.14 40.74
🟢   auth.ts 83.33 100 66.66 83.33 15
🔴   db.ts 31.25 50 50 31.25 15-35,45,47
🟢   logger.ts 100 100 100 100
🔴   tracer.ts 0 100 100 0 5-14
🟡  services/agencies 70.58 0 83.33 70.58
🟢   agencies.scenarios.ts 100 100 100 100
🟡   agencies.ts 68.75 0 83.33 68.75 40-48
🟢  services/expenditureCategories 92.3 100 83.33 92.3
🟢   expenditureCategories.scenarios.ts 100 100 100 100
🟢   expenditureCategories.ts 91.66 100 83.33 91.66 46
🟢  services/inputTemplates 92.3 100 83.33 92.3
🟢   inputTemplates.scenarios.ts 100 100 100 100
🟢   inputTemplates.ts 91.66 100 83.33 91.66 47
🟢  services/organizations 92.3 100 83.33 92.3
🟢   organizations.scenarios.ts 100 100 100 100
🟢   organizations.ts 91.66 100 83.33 91.66 47
🟢  services/outputTemplates 92.3 100 83.33 92.3
🟢   outputTemplates.scenarios.ts 100 100 100 100
🟢   outputTemplates.ts 91.66 100 83.33 91.66 43
🟡  services/projects 80 100 62.5 80
🟢   projects.scenarios.ts 100 100 100 100
🟡   projects.ts 78.57 100 62.5 78.57 45-51
🟡  services/reportingPeriods 80 100 62.5 80
🟢   reportingPeriods.scenarios.ts 100 100 100 100
🟡   reportingPeriods.ts 78.57 100 62.5 78.57 43-53
🟢  services/roles 92.3 100 83.33 92.3
🟢   roles.scenarios.ts 100 100 100 100
🟢   roles.ts 91.66 100 83.33 91.66 40
🟡  services/subrecipients 80 100 62.5 80
🟢   subrecipients.scenarios.ts 100 100 100 100
🟡   subrecipients.ts 78.57 100 62.5 78.57 47-55
🟡  services/uploadValidations 66.66 100 45.45 66.66
🟢   uploadValidations.scenarios.ts 100 100 100 100
🟡   uploadValidations.ts 64.7 100 45.45 64.7 45-66
🟡  services/uploads 66.66 100 45.45 66.66
🟢   uploads.scenarios.ts 100 100 100 100
🟡   uploads.ts 64.7 100 45.45 64.7 43-60
🟡  services/users 75 100 55.55 75
🟢   users.scenarios.ts 100 100 100 100
🟡   users.ts 73.33 100 55.55 73.33 40-49
Coverage report for web suite
St File % Stmts % Branch % Funcs % Lines Uncovered Line #s
🔴 All files 23.84 25 21.42 22.32
🔴  src 15.38 0 50 15.38
🔴   App.tsx 0 0 0 0 3-32
🟢   Routes.tsx 100 100 100 100
🔴   entry.client.tsx 0 0 100 0 10-22
🔴  src/components/Agency/Agencies 0 100 0 0
🔴   Agencies.tsx 0 100 0 0 9-21
🔴  src/components/Agency/AgenciesCell 0 100 0 0
🔴   AgenciesCell.tsx 0 100 0 0 8-39
🔴  src/components/Agency/Agency 0 0 0 0
🔴   Agency.tsx 0 0 0 0 10-78
🔴  src/components/Agency/AgencyCell 0 100 0 0
🔴   AgencyCell.tsx 0 100 0 0 7-27
🔴  src/components/Agency/AgencyForm 0 0 0 0
🔴   AgencyForm.tsx 0 0 0 0 24-39
🔴  src/components/Agency/EditAgencyCell 0 100 0 0
🔴   EditAgencyCell.tsx 0 100 0 0 10-58
🔴  src/components/Agency/NewAgency 0 100 0 0
🔴   NewAgency.tsx 0 100 0 0 9-35
🔴  src/components/Organization/EditOrganizationCell 0 100 0 0
🔴   EditOrganizationCell.tsx 0 100 0 0 13-62
🔴  src/components/Organization/NewOrganization 0 100 0 0
🔴   NewOrganization.tsx 0 100 0 0 9-35
🔴  src/components/Organization/Organization 0 0 0 0
🔴   Organization.tsx 0 0 0 0 10-70
🔴  src/components/Organization/OrganizationCell 0 100 0 0
🔴   OrganizationCell.tsx 0 100 0 0 7-27
🔴  src/components/Organization/OrganizationForm 0 0 0 0
🔴   OrganizationForm.tsx 0 0 0 0 27-41
🔴  src/components/Organization/Organizations 0 100 0 0
🔴   Organizations.tsx 0 100 0 0 9-21
🔴  src/components/Organization/OrganizationsCell 0 100 0 0
🔴   OrganizationsCell.tsx 0 100 0 0 8-37
🟡  src/components/ReportingPeriodCell 55 0 55.55 47.05
🟢   ReportingPeriodCell.mock.ts 100 100 100 100
🔴   ReportingPeriodCell.stories.tsx 0 0 0 0 6-32
🟢   ReportingPeriodCell.tsx 100 100 100 100
🟡  src/components/ReportingPeriodsCell 57.14 28.57 60 50
🟢   ReportingPeriodsCell.mock.ts 100 100 100 100
🔴   ReportingPeriodsCell.stories.tsx 0 0 0 0 6-32
🟢   ReportingPeriodsCell.tsx 100 66.66 100 100 63-66
🟡  src/layouts/ScaffoldLayout 50 100 0 50
🟡   ScaffoldLayout.tsx 50 100 0 50 10
🟢  src/lib 100 100 100 100
🟢   formatters.tsx 100 100 100 100
🔴  src/pages/Agency/AgenciesPage 0 100 0 0
🔴   AgenciesPage.tsx 0 100 0 0 7-11
🔴  src/pages/Agency/AgencyPage 0 100 0 0
🔴   AgencyPage.tsx 0 100 0 0 7-8
🔴  src/pages/Agency/EditAgencyPage 0 100 0 0
🔴   EditAgencyPage.tsx 0 100 0 0 7-8
🔴  src/pages/Agency/NewAgencyPage 0 100 0 0
🔴   NewAgencyPage.tsx 0 100 0 0 3-4
🔴  src/pages/FatalErrorPage 0 0 0 0
🔴   FatalErrorPage.tsx 0 0 0 0 15
🔴  src/pages/NotFoundPage 0 100 0 0
🔴   NotFoundPage.tsx 0 100 0 0 2
🔴  src/pages/Organization/EditOrganizationPage 0 100 0 0
🔴   EditOrganizationPage.tsx 0 100 0 0 7-8
🔴  src/pages/Organization/NewOrganizationPage 0 100 0 0
🔴   NewOrganizationPage.tsx 0 100 0 0 3-4
🔴  src/pages/Organization/OrganizationPage 0 100 0 0
🔴   OrganizationPage.tsx 0 100 0 0 7-8
🔴  src/pages/Organization/OrganizationsPage 0 100 0 0
🔴   OrganizationsPage.tsx 0 100 0 0 7-8
🟡  src/pages/ReportingPeriodsPage 50 100 100 50
🔴   ReportingPeriodsPage.stories.tsx 0 100 100 0 5-13
🟢   ReportingPeriodsPage.tsx 100 100 100 100
🟡  src/pages/UploadTemplatePage 50 100 50 50
🔴   UploadTemplatePage.stories.tsx 0 100 100 0 5-13
🟡   UploadTemplatePage.tsx 75 100 50 75 9

Pusher: @TylerHendrickson, Action: pull_request_target, Workflow: Continuous Integration

@as1729 as1729 merged commit b80111b into main Dec 12, 2023
19 checks passed
@as1729 as1729 deleted the fix/spa-routing-responses branch December 12, 2023 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working infra terraform
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants